﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Linq;
using ConfigGen.Core;
using ConfigGen.Utilities.Extensions.System;
using log4net.Appender;
using log4net.Config;
using log4net.Filter;
using log4net.Layout;
using Microsoft.Practices.Unity;
using NAnt.Core;
using NAnt.Core.Attributes;

namespace ConfigGen.Applications.NAnt
{
    /// <summary>
    /// NAnt task for running ConfigGen
    /// </summary>
    [TaskName("configgen")]
    public class ConfigGenTask : Task
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigGenTask"/> class.
        /// </summary>
        public ConfigGenTask()
        {
            FailOnWarnings = true;
        }

        #endregion

        #region Task Overrides

        /// <summary>
        /// Executes the task.
        /// </summary>
        protected override void ExecuteTask()
        {
            try
            {
                var version = typeof(ConfigurationFilesGenerator).Assembly.GetName().Version;
                Log(Level.Info, "ConfigGen NAnt Task v{0}", version);
                Log(Level.Info, "Copyright (C)2010 - Rob Levine and other contributors - http://configgen.codeplex.com");
                Log(Level.Info, "--");

                var settings = new Preferences
                                   {
                                       DumpSettingsOnly = DumpSettingsOnly,
                                       DumpSettingsFile = DumpSettingsFile,
                                       FlatDirectoryOutput = FlatDirectoryOutput,
                                       ForcedFilename = ForceName,
                                       InhibitWrite = InhibitWrite,
                                       LocalOnly = LocalOnly,
                                       MachineSuffix = AddMachineNameSuffix,
                                       OutputDirectory = OutputDirectory ?? "Configs",
                                       PrettyPrint = PrettyPrint,
                                       PrettyPrintLineLength = PrettyPrintLineLength == 0 ? (int?)null : PrettyPrintLineLength,
                                       SettingsFile = SettingsFile,
                                       SpecifiedMachines = MachineNames.IsNullOrEmpty() ? null : MachineNames.Split(new[] { ',', ' ' }),
                                       TemplateFile = TemplateFile,
                                       Verbose = Verbose,
                                       WriteUnchangedFiles = WriteUnchangedFiles
                                   };
                ConfigureLogging(Verbose);
                bool warningsGenerated = false;
                var container = new ConfigGenContainer();
                var generator = container.Resolve<ConfigurationFilesGenerator>();
                var numResults = generator.GenerateFiles(settings);

                if (numResults == 0)
                {
                    Log(Level.Info, "No files generated.");
                    return;
                }

                foreach (var result in generator.Results)
                {
                    if (result == null)
                    {
                        break;
                    }
                    var hasWarnings = result.HasWarnings;
                    warningsGenerated |= hasWarnings;
                    if (hasWarnings)
                    {
                        Log(Level.Warning, "{0} - {1} - WITH WARNINGS",
                                       result.MachineName.PadRight(20, ' '),
                                       result.OldFileMd5Hash.IsEqualTo(result.NewFileMd5Hash)
                                           ? "[FILE UNCHANGED]"
                                           : "[ FILE CHANGED ]");
                    }
                    else
                    {
                        Log(Level.Info, "{0} - {1} - WITHOUT WARNINGS",
                                       result.MachineName.PadRight(20, ' '),
                                       result.OldFileMd5Hash.IsEqualTo(result.NewFileMd5Hash)
                                           ? "[FILE UNCHANGED]"
                                           : "[FILE CHANGED  ]");
                    }
                    if (hasWarnings)
                    {
                        foreach (var item in result.UnrecognisedTokenCounts.Where(t => t.Value > 0))
                        {
                            Log(Level.Warning, "Unrecognised token in template: " + item.Key);
                        }

                        foreach (var item in result.TokenUseCounts.Where(t => t.Value == 0))
                        {
                            Log(Level.Warning, "Unused token in settings: " + item.Key);
                        }
                        Log(Level.Info, String.Empty);
                    }
                }

                if (warningsGenerated)
                {
                    Log(Level.Warning, String.Empty);
                    Log(Level.Warning, "Configuration generation completed with warnings");
                    Log(Level.Warning, String.Empty);
                    if (FailOnWarnings)
                    {
                        throw new BuildException("Build failure as FailOnWarnings was specified and one or more warnings were generated");
                    }
                }
                else
                {
                    Log(Level.Info, String.Empty);
                    Log(Level.Info, "Configuration generation completed without warnings");
                    Log(Level.Info, String.Empty);
                }
            }
            catch (BuildException)
            {
                throw;
            }
            catch (Exception ex)
            {
                Log(Level.Error, "Unhandled exception in ConfigGenTask: {0} - {1}\n{2}", ex.GetType(), ex.Message, ex);
                throw;
            }
            finally
            {
                Log(Level.Info, "NAnt task exiting.");
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the AddMachineNameSuffix setting. See <see cref="Preferences.MachineSuffix"/> for more information.
        /// </summary>
        [TaskAttribute("addMachineNameSuffix")]
        public bool AddMachineNameSuffix { get; set; }

        /// <summary>
        /// Gets or sets the DumpSettingsOnly setting. See <see cref="Preferences.DumpSettingsOnly"/> for more information.
        /// </summary>
        [TaskAttribute("dumpSettingsOnly")]
        public bool DumpSettingsOnly { get; set; }

        /// <summary>
        /// Gets or sets the DumpSettingsFile setting. See <see cref="Preferences.DumpSettingsFile"/> for more information.
        /// </summary>
        [TaskAttribute("dumpSettingsFile")]
        public string DumpSettingsFile { get; set; }

        /// <summary>
        /// Gets or sets the FailOnWarnings setting. If true, the NAnt build task will fail if any warnings are raised during configuration generation.
        /// If false, the task will only fail if an error occurs, not on a warning only.
        /// </summary>
        [TaskAttribute("failOnWarnings")]
        public bool FailOnWarnings { get; set; }

        /// <summary>
        /// Gets or sets the ForceName setting. See <see cref="Preferences.ForcedFilename"/> for more information.
        /// </summary>
        [TaskAttribute("forceName")]
        public string ForceName { get; set; }

        /// <summary>
        /// Gets or sets the InhibitWrite setting. See <see cref="Preferences.InhibitWrite"/> for more information.
        /// </summary>
        [TaskAttribute("inhibitWrite")]
        public bool InhibitWrite { get; set; }

        /// <summary>
        /// Gets or sets the LocalOnly setting. See <see cref="Preferences.LocalOnly"/> for more information.
        /// </summary>
        [TaskAttribute("localOnly")]
        public bool LocalOnly { get; set; }

        /// <summary>
        /// Gets or sets the MachineNames setting. See <see cref="Preferences.SpecifiedMachines"/> for more information.
        /// </summary>
        [TaskAttribute("machineNames")]
        public string MachineNames { get; set; }

        /// <summary>
        /// Gets or sets the OutputDirectory setting. See <see cref="Preferences.OutputDirectory"/> for more information.
        /// </summary>
        [TaskAttribute("outputDirectory")]
        public string OutputDirectory { get; set; }

        /// <summary>
        /// Gets or sets the PrettyPrint setting. See <see cref="Preferences.PrettyPrint"/> for more information.
        /// </summary>
        [TaskAttribute("prettyPrint")]
        public bool PrettyPrint { get; set; }

        /// <summary>
        /// Gets or sets the PrettyPrintLineLength setting. See <see cref="Preferences.PrettyPrintLineLength"/> for more information.
        /// </summary>
        [TaskAttribute("prettyPrintLineLength")]
        public int PrettyPrintLineLength { get; set; }

        /// <summary>
        /// Gets or sets the SettingsFile setting. See <see cref="Preferences.SettingsFile"/> for more information.
        /// </summary>
        [TaskAttribute("settingsFile", Required = true)]
        public string SettingsFile { get; set; }

        /// <summary>
        /// Gets or sets the FlatDirectoryOutput setting. See <see cref="Preferences.FlatDirectoryOutput"/> for more information.
        /// </summary>
        [TaskAttribute("flatDirectoryOutput")]
        public bool FlatDirectoryOutput { get; set; }

        /// <summary>
        /// Gets or sets the TemplateFile setting. See <see cref="Preferences.TemplateFile"/> for more information.
        /// </summary>
        [TaskAttribute("templateFile", Required = true)]
        public string TemplateFile { get; set; }

        /// <summary>
        /// Gets or sets the WriteUnchangedFiles setting. See <see cref="Preferences.WriteUnchangedFiles"/> for more information.
        /// </summary>
        [TaskAttribute("writeUnchangedFiles")]
        public bool WriteUnchangedFiles { get; set; }

        #endregion

        #region Private Methods

        private static readonly ConsoleAppender ConsoleOutAppender = new ConsoleAppender();
        private static readonly ConsoleAppender ConsoleErrorAppender = new ConsoleAppender();

        private static void ConfigureLogging(bool verbose)
        {
            const string layout = @"[configgen] %message%newline";
            ConsoleOutAppender.Threshold = verbose ? log4net.Core.Level.Debug : log4net.Core.Level.Info;
            ConsoleOutAppender.AddFilter(new LevelMatchFilter() { AcceptOnMatch = false, LevelToMatch = log4net.Core.Level.Error });
            ConsoleOutAppender.AddFilter(new LevelMatchFilter() { AcceptOnMatch = false, LevelToMatch = log4net.Core.Level.Fatal });
            ConsoleOutAppender.Layout = new PatternLayout(layout);
            ConsoleOutAppender.ActivateOptions();
            BasicConfigurator.Configure(ConsoleOutAppender);

            ConsoleErrorAppender.Target = "Console.Error";
            ConsoleErrorAppender.Threshold = log4net.Core.Level.Error;
            ConsoleErrorAppender.Layout = new PatternLayout(layout);
            ConsoleErrorAppender.ActivateOptions();
            BasicConfigurator.Configure(ConsoleErrorAppender);
        }

        #endregion
    }
}